package com.trade.okxserver.clr.trade;

import com.alibaba.excel.EasyExcel;
import com.google.gson.Gson;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.trade.okxserver.api.PublicData;
import com.trade.okxserver.config.OkxProperties;
import com.trade.okxserver.enums.TimeRange;
import com.trade.okxserver.operator.ScaleOperator;
import com.trade.okxserver.operator.StandardOperator;
import com.trade.okxserver.operator.VarianceOperator;
import com.trade.okxserver.req.KLineReq;
import com.trade.okxserver.res.KlineRes;
import com.trade.okxserver.res.OrderLog;
import com.trade.okxserver.res.PublicDataRes;
import com.trade.okxserver.util.ExpressUtils;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Slf4j
@Component
public class KlineCommand implements CommandLineRunner {

    private static final List<String> DEFAULT_VARIABLES = List.of("M5", "M10", "20", "L5", "L10", "L20");

    private static OrderLog order;

    @Resource
    PublicData publicData;

    @Resource
    OkxProperties properties;
    @Autowired
    private OkxProperties okxProperties;

    private Gson gson = new Gson();

    @Override
    @SneakyThrows
    public void run(String... args) {
        log.info("开始拉去K线数据");
        // 时间解析
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Date startTime = DateUtils.parseDate(properties.getKLine().getStartTime(), new String[]{"yyyy-MM-dd HH:mm:ss"});
        Date endTime = DateUtils.parseDate(properties.getKLine().getEndTime(), new String[]{"yyyy-MM-dd HH:mm:ss"});
        OkxProperties.KLineConfig kLine = properties.getKLine();
        TimeRange timeRange = kLine.getTimeRange();
        long milliseconds = startTime.getTime() - endTime.getTime();
        long groups = milliseconds / timeRange.getMilliseconds() / 100 + 1;
        Long startTimeTemp = startTime.getTime();
        List<CompletableFuture<List<List<String>>>> completableFutures = new ArrayList<>();
        for (int i = 0; i < groups; i++) {
            Long endTimeTemp = startTimeTemp - (timeRange.getMilliseconds() * 100);
            String startTimeStr = String.valueOf(startTimeTemp);
            String endTimeStr = String.valueOf(endTimeTemp);
            KLineReq kLineReq = new KLineReq(properties.getInstId(), properties.getKLine().getTimeRange().getLabel(), endTimeStr, startTimeStr, null);
            CompletableFuture<List<List<String>>> listCompletableFuture = CompletableFuture.supplyAsync(() -> publicData.kLineHistory(kLineReq));
            completableFutures.add(listCompletableFuture);
            startTimeTemp = endTimeTemp;
        }
        CompletableFuture<List<List<String>>> listCompletableFuture = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[]{}))
                .thenApply((v) -> completableFutures.stream()
                        .map(CompletableFuture::join)
                        .flatMap(List::stream)
                        .collect(Collectors.toList()));

        List<List<String>> list = listCompletableFuture.join();
        stopWatch.stop();

        List<KlineRes> results = list.parallelStream()
                .map(item -> {
                    KlineRes klineRes = new KlineRes();
                    klineRes.setTs(item.get(0));
                    klineRes.setO(item.get(1));
                    klineRes.setH(item.get(2));
                    klineRes.setL(item.get(3));
                    klineRes.setC(item.get(4));
                    klineRes.setVol(item.get(5));
                    klineRes.setVolCcy(item.get(6));
                    klineRes.setVolCcyQuote(item.get(7));
                    klineRes.setConfirm(item.get(8));
                    return klineRes;
                }).sorted(Comparator.comparingLong(o -> Long.parseLong(o.getTs()))).collect(Collectors.toList());

        if (CollectionUtils.isEmpty(results)) {
            log.info("K线数据不存在");
            return;
        }

        log.info("开始拉去进行数据复盘。。。");
        int total = 0;
        int winTotal = 0;
        int failTotal = 0;
        ArrayList<OrderLog> orderLogs = new ArrayList<>();
        BigDecimal gh = BigDecimal.ZERO;
        BigDecimal gl = BigDecimal.valueOf(Double.MAX_VALUE);
        DefaultContext<String, Object> context = new DefaultContext<>();
        loadScale(context);
        context.put("GK", new BigDecimal(results.get(0).getO()));
        List<String> variables = properties.getVariables();
        variables.addAll(DEFAULT_VARIABLES);
        for (int i = 0; i < results.size(); i++) {
            context.put("GS", new BigDecimal(results.get(i).getC()));
            if (gh.compareTo(new BigDecimal(results.get(i).getH())) < 0) {
                gh = new BigDecimal(results.get(i).getH());
                context.put("GH", new BigDecimal(results.get(i).getH()));
            }
            if (gl.compareTo(new BigDecimal(results.get(i).getL())) > 0) {
                gl = new BigDecimal(results.get(i).getL());
                context.put("GL", new BigDecimal(results.get(i).getL()));
            }

            context.put("CK", new BigDecimal(results.get(i).getO()));
            context.put("CS", new BigDecimal(results.get(i).getC()));
            context.put("CH", new BigDecimal(results.get(i).getH()));
            context.put("CL", new BigDecimal(results.get(i).getL()));


            // 加载变量
            for (String variable : variables) {
                if (StringUtils.startsWith(variable, "M")) {
                    String m1 = StringUtils.removeStart(variable, "M");
                    if (StringUtils.isBlank(m1)) {
                        continue;
                    }
                    int m = Integer.parseInt(m1);
                    int end = i;
                    int start = 0;
                    if (i > m) {
                        start = i - m;
                    }
                    loadM(context, variable, results.subList(start, end));
                } else if (StringUtils.startsWith(variable, "L")) {
                    String l1 = StringUtils.removeStart(variable, "L");
                    if (StringUtils.isBlank(l1)) {
                        continue;
                    }
                    int m = Integer.parseInt(l1);
                    int end = i;
                    int start = 0;
                    if (i > m) {
                        start = i - m;
                    }
                    loadL(context, variable, results.subList(start, end));
                } else if (StringUtils.startsWith(variable, "K")) {
                    String k = StringUtils.removeStart(variable, "K");
                    if (StringUtils.isBlank(k)) {
                        continue;
                    }
                    int kIndex = Integer.parseInt(k);
                    kIndex = i - kIndex;
                    if (kIndex < 0) {
                        kIndex = 0;
                    }
                    loadK(context, variable, results.get(kIndex));
                }
            }

            // 自定义规则变量
            for (Map<String, String> customVariableRule : okxProperties.getCustomVariableRules()) {
                String variable = customVariableRule.get("variable");
                Object value = ExpressUtils.execute(customVariableRule.get("rule"), context);
                context.put(variable, value);
            }

            log.info("K线：{}， 变量：{}", gson.toJson(results.get(i)), gson.toJson(context));
            // 订单不存在 校验买入
            if (order != null) {
                // 订单存在校验卖出
                //   1、买入价格*1.02     （盈利比例）
                //   2、买入价格*0.99     （止损比例）
                context.put("buyPrice", new BigDecimal(order.getBuyPrice()));
                String rule = String.format("if (CL<= buyPrice * %s) then {return 2;} else if (CH >= buyPrice * %s) then {return 1;} else  {return 0;}", okxProperties.getLoss(), okxProperties.getProfit());
                String resultStr = ExpressUtils.execute(rule, context).toString();
                if (StringUtils.equals(resultStr, "2")) {
                    BigDecimal buyPrice = new BigDecimal(order.getBuyPrice());
                    BigDecimal sellPrice = buyPrice.multiply(new BigDecimal(okxProperties.getLoss()));
                    order.setSellPrice(sellPrice.toString());
                    order.setSellTime(tsFormat(results.get(i).getTs()));
                    order.setDiffPrice("-" + buyPrice.subtract(sellPrice).abs());
                    order.setProfit(false);

                    orderLogs.add(order);
                    order = null;
                    failTotal += 1;
                } else if (StringUtils.equals(resultStr, "1")) {
                    BigDecimal buyPrice = new BigDecimal(order.getBuyPrice());
                    BigDecimal sellPrice = buyPrice.multiply(new BigDecimal(okxProperties.getProfit()));
                    order.setSellPrice(sellPrice.toString());
                    order.setSellTime(tsFormat(results.get(i).getTs()));
                    order.setDiffPrice(buyPrice.subtract(sellPrice).abs().toString());
                    order.setProfit(true);
                    orderLogs.add(order);
                    order = null;
                    winTotal += 1;
                }
            }

            if (order == null) {
                // 购买规则
                Boolean flag = Boolean.parseBoolean(ExpressUtils.execute(okxProperties.getBuyRule(), context).toString());
                if (flag) {
                    order = new OrderLog();
                    order.setBuyTime(tsFormat(results.get(i).getTs()));
                    order.setBuyPrice(context.get("buyPrice").toString());
                    total += 1;
                }
            }
        }

        simpleWrite(orderLogs);
        log.info("拉去K线总耗时：{}ms", stopWatch.getTotalTimeMillis());
        log.info("买入次数: {} ,正常卖出次数: {}, 止损卖出次数: {}", total, winTotal, failTotal);
//        publicData.instruments()
//                .subscribe(response -> {
//                    for (PublicDataRes publicDataRes : response) {
//                        KLineReq kLineReq = new KLineReq(publicDataRes.getInstId(), String.valueOf(properties.getKLine().getTimeRange().getMilliseconds()), null, null, null);
//                        publicData.kLine(kLineReq)
//                                .subscribe(item -> System.out.println(gson.toJson(item)));
//                    }
//                });

    }

    @SneakyThrows
    private void loadScale(DefaultContext<String, Object> context) {
        List<PublicDataRes> instruments = publicData.instruments(null);
        PublicDataRes publicDataRes = instruments.stream()
                .filter(item -> item.getInstId().equals(properties.getInstId()))
                .findFirst()
                .get();
        ExpressRunner runner = ExpressUtils.getRunner();
        context.put("priceScale", new BigDecimal(publicDataRes.getTickSz()));
        runner.addFunction("scale", new ScaleOperator(new BigDecimal(publicDataRes.getTickSz())));
    }

    public void simpleWrite(List<OrderLog> orderLogs) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String format = simpleDateFormat.format(new Date());
        String fileName =  "复盘数据总结" + format + ".xlsx";
        EasyExcel.write(fileName, OrderLog.class).sheet("模板").doWrite(orderLogs);
    }

    private void loadK(DefaultContext<String, Object> context, String variable, KlineRes klineRes) {
        context.put(variable + "C", new BigDecimal(klineRes.getC()));
        context.put(variable + "O", new BigDecimal(klineRes.getO()));
        context.put(variable + "H", new BigDecimal(klineRes.getH()));
        context.put(variable + "L", new BigDecimal(klineRes.getL()));
        context.put(variable + "Vol", new BigDecimal(klineRes.getVol()));

        // 销量/（K线最高价格-K线最低价格）*价格精度
        BigDecimal priceScale = (BigDecimal) context.get("priceScale");
        BigDecimal result = new BigDecimal(klineRes.getVol())
                .divide(new BigDecimal(klineRes.getH()).subtract(new BigDecimal(klineRes.getL())), RoundingMode.HALF_UP)
                        .multiply(priceScale);
        context.put(variable + "D", result);
    }

    @SneakyThrows
    private void loadM(DefaultContext<String, Object> context, String variable, List<KlineRes> klineResList) {
        if (klineResList.isEmpty()) {
            context.put(variable + "C", 0);
            context.put(variable + "O", 0);
            context.put(variable + "H", 0);
            context.put(variable + "L", 0);
            context.put(variable + "C_STD", 0);
            context.put(variable + "O_STD", 0);
            context.put(variable + "H_STD", 0);
            context.put(variable + "L_STD", 0);
            context.put(variable + "C_VAR", 0);
            context.put(variable + "O_VAR", 0);
            context.put(variable + "H_VAR", 0);
            context.put(variable + "L_VAR", 0);
            return;
        }

        // 均值
        BigDecimal totalC = BigDecimal.ZERO;
        BigDecimal totalO = BigDecimal.ZERO;
        BigDecimal totalH = BigDecimal.ZERO;
        BigDecimal totalL = BigDecimal.ZERO;
        for (KlineRes klineRes : klineResList) {
            totalC = totalC.add(new BigDecimal(klineRes.getC()));
            totalO = totalO.add(new BigDecimal(klineRes.getC()));
            totalH = totalH.add(new BigDecimal(klineRes.getC()));
            totalL = totalL.add(new BigDecimal(klineRes.getC()));
        }
        context.put(variable + "C", totalC.divide(new BigDecimal(klineResList.size()), 2, RoundingMode.HALF_UP));
        context.put(variable + "O", totalC.divide(new BigDecimal(klineResList.size()), 2, RoundingMode.HALF_UP));
        context.put(variable + "H", totalC.divide(new BigDecimal(klineResList.size()), 2, RoundingMode.HALF_UP));
        context.put(variable + "L", totalC.divide(new BigDecimal(klineResList.size()), 2, RoundingMode.HALF_UP));

        // 标准差
        StandardOperator standardOperator = new StandardOperator();
        context.put(variable + "C_STD", standardOperator.executeInner(klineResList.stream().map(KlineRes::getC).toArray()));
        context.put(variable + "O_STD", standardOperator.executeInner(klineResList.stream().map(KlineRes::getO).toArray()));
        context.put(variable + "H_STD", standardOperator.executeInner(klineResList.stream().map(KlineRes::getH).toArray()));
        context.put(variable + "L_STD", standardOperator.executeInner(klineResList.stream().map(KlineRes::getL).toArray()));

        // 方差
        VarianceOperator varianceOperator = new VarianceOperator();
        context.put(variable + "C_VAR", varianceOperator.executeInner(klineResList.stream().map(KlineRes::getC).toArray()));
        context.put(variable + "O_VAR", varianceOperator.executeInner(klineResList.stream().map(KlineRes::getO).toArray()));
        context.put(variable + "H_VAR", varianceOperator.executeInner(klineResList.stream().map(KlineRes::getH).toArray()));
        context.put(variable + "L_VAR", varianceOperator.executeInner(klineResList.stream().map(KlineRes::getL).toArray()));
    }

    @SneakyThrows
    private void loadL(DefaultContext<String, Object> context, String variable, List<KlineRes> klineResList) {
        if (klineResList.isEmpty()) {
            context.put(variable, 0);
            context.put(variable + "_STD", 0);
            context.put(variable + "_VAR", 0);
            return;
        }
        BigDecimal total = BigDecimal.ZERO;
        for (KlineRes klineRes : klineResList) {
            total = total.add(new BigDecimal(klineRes.getVol()));
        }
        context.put(variable, total.divide(new BigDecimal(klineResList.size()), 2, RoundingMode.HALF_UP));

        // 标准差
        StandardOperator standardOperator = new StandardOperator();
        context.put(variable + "_STD", standardOperator.executeInner(klineResList.stream().map(KlineRes::getVol).toArray()));

        // 方差
        VarianceOperator varianceOperator = new VarianceOperator();
        context.put(variable + "_VAR", varianceOperator.executeInner(klineResList.stream().map(KlineRes::getVol).toArray()));
    }

    private String tsFormat(String ts) {
        Date date = new Date(Long.parseLong(ts));
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        return simpleDateFormat.format(date);
    }

}